﻿using System.Text.RegularExpressions;
using System.Web;

namespace CodeContrib.Web
{
	public static class PathExtensions
	{
		private const string _urlRewriterServerVariable = "HTTP_X_ORIGINAL_URL";

		// This method can accept an app-relative path or an absolute path for contentPath.
		public static string GenerateClientUrl(this HttpContextBase httpContext, string contentPath)
		{
			if (string.IsNullOrEmpty(contentPath))
			{
				return contentPath;
			}

			// Many of the methods we call internally can't handle query strings properly, so just strip it out for the time being.
			string query;

			contentPath = RemoveQueryString(contentPath, out query);

			// Can't call VirtualPathUtility.IsAppRelative since it throws on some inputs.
			var isAppRelative = contentPath[0] == '~';

			if (isAppRelative)
			{
				var absoluteContentPath = VirtualPathUtility.ToAbsolute(contentPath, httpContext.Request.ApplicationPath);

				var modifiedAbsoluteContentPath = httpContext.Response.ApplyAppPathModifier(absoluteContentPath);

				return GenerateClientUrl(httpContext, modifiedAbsoluteContentPath);
			}

			// We only want to manipulate the path if URL rewriting is active, else we risk breaking the generated URL.
			var serverVars = httpContext.Request.ServerVariables;

			var urlRewriterIsEnabled = (serverVars != null && serverVars[_urlRewriterServerVariable] != null);

			if (!urlRewriterIsEnabled)
			{
				return contentPath;
			}

			// Since the rawUrl represents what the user sees in his browser, it is what we want to use as the base
			// of our absolute paths. For example, consider mysite.example.com/foo, which is internally
			// rewritten to content.example.com/mysite/foo. When we want to generate a link to ~/bar, we want to
			// base it from / instead of /foo, otherwise the user ends up seeing mysite.example.com/foo/bar,
			// which is incorrect.
			var relativeUrlToDestination = ToRelativePath(httpContext.Request.Path, contentPath);

			var absoluteUrlToDestination = ToAbsolutePath(httpContext.Request.RawUrl, relativeUrlToDestination);

			return absoluteUrlToDestination;
		}

		public static string RemoveQueryString(this string path, out string queryString)
		{
			var queryIndex = path.IndexOf('?');

			if (queryIndex >= 0)
			{
				queryString = path.Substring(queryIndex);

				return path.Substring(0, queryIndex);
			}

			return queryString = null;
		}

		public static string ToAbsolutePath(this string basePath, string relativePath)
		{
			// The Combine() method can't handle query strings on the base path, so we trim it off.
			string query;

			basePath = RemoveQueryString(basePath, out query);

			return VirtualPathUtility.Combine(basePath, relativePath);
		}

		public static string ToClientUrl(this string virtualPath, string applicationPath, string currentPath)
		{
			// We try to go from an absolute URL to an app-relative URL to a relative URL
			// to enable things like URL rewriting and session state.

			// VirtualPathUtility.ToAppRelative() bombs out if the path it's given contains a '?' character.
			var queryStringIndex = virtualPath.IndexOf('?');

			var virtualPathWithoutQueryString = (queryStringIndex == -1) ? virtualPath : virtualPath.Substring(0, queryStringIndex);

			string appRelativePath = null;

			try
			{
				appRelativePath = VirtualPathUtility.ToAppRelative(virtualPathWithoutQueryString, applicationPath);
			}
			catch
			{
				// We might get an error if Routing returns a path of a form we're not expecting.
				// In that case, just return the original URL without running client resolution logic.
			}

			if (string.IsNullOrEmpty(appRelativePath))
			{
				return virtualPath;
			}

			// Session state URLs look like /AppPath/(S(...))/LocalPath, so an "app-relative" path
			// containing session state looks like ~/(S(...))/LocalPath. The Regex below matches
			// the "(S(...))/" part of that URL for easy removal.
			var sessionStateRegex = new Regex(@"(?<=^~/)\(S\([^/?]*\)\)/", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);

			if (appRelativePath != null)
			{
				appRelativePath = sessionStateRegex.Replace(appRelativePath, string.Empty);
			}

			var destinationPath = VirtualPathUtility.ToAbsolute(appRelativePath, applicationPath);

			// Restore query string portion.
			if (queryStringIndex >= 0)
			{
				destinationPath += virtualPath.Substring(queryStringIndex, virtualPath.Length - queryStringIndex);
			}

			var clientUrl = ToRelativePath(currentPath, destinationPath);

			return clientUrl;
		}

		public static string ToRelativePath(this string fromPath, string toPath)
		{
			var relativeUrl = VirtualPathUtility.MakeRelative(fromPath, toPath);

			if (string.IsNullOrEmpty(relativeUrl)
			    || relativeUrl[0] == '?')
			{
				// Sometimes VirtualPathUtility.MakeRelative() will return an empty string when it meant to return '.',
				// but links to {empty string} are browser dependent. We replace it with an explicit path to force
				// consistency across browsers.
				relativeUrl = "./" + relativeUrl;
			}

			return relativeUrl;
		}
	}
}